Skip to content

feat: #110 Add pairing table assignments#111

Merged
ianpaschal merged 1 commit intodevelopfrom
feat-110-add-pairing-table-assignments
Jul 9, 2025
Merged

feat: #110 Add pairing table assignments#111
ianpaschal merged 1 commit intodevelopfrom
feat-110-add-pairing-table-assignments

Conversation

@ianpaschal
Copy link
Owner

@ianpaschal ianpaschal commented Jul 7, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a dedicated Tournament Pairings page for setting up and confirming round pairings, featuring drag-and-drop sorting, table assignment, and competitor validation.
    • Added visual pairing status indicators and info popovers for enhanced pairing feedback.
    • New warning and pulsar components for improved UI feedback.
    • Added a sortable grid component for flexible item arrangement.
  • Enhancements

    • Improved confirmation dialogs for round configuration, with clearer warnings and validation.
    • Updated tournament context menus and detail cards to streamline round management and configuration actions.
    • Enhanced popover and table components with orientation and index support.
  • Bug Fixes

    • Corrected tournament timer initialization and match result progress calculations.
  • Refactor

    • Simplified and reorganized pairing logic and error handling.
    • Renamed and restructured tournament round mutations and related hooks for clarity.
  • Removals

    • Removed the old Tournament Advance Round wizard and its related components and styles.
    • Eliminated deprecated drag-and-drop pairing grid and associated styles.
  • Documentation

    • Updated tournament lifecycle documentation to reflect new round mutation names.

@vercel
Copy link

vercel bot commented Jul 7, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
combat-command ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 8, 2025 6:44pm

@coderabbitai
Copy link

coderabbitai bot commented Jul 7, 2025

Walkthrough

This update removes the old multi-step round advancement wizard and drag-and-drop pairing grid, replacing them with a new, form-driven Tournament Pairings Page. The new implementation introduces sortable grid components, pairing validation, table assignment utilities, and confirmation dialogs, with extensive refactoring of pairing creation logic, error handling, and context menu actions. Numerous obsolete components, styles, and utilities are deleted, and related routes, exports, and service hooks are updated to reflect the new pairing workflow.

Changes

Files/Group Change Summary
src/pages/TournamentAdvanceRoundPage/**, src/components/TournamentPairingsGrid/** Deleted the multi-step round advancement wizard, drag-and-drop pairing grid, and all related components, types, utilities, and styles.
src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx, TournamentPairingsPage.module.scss,
TournamentPairingsPage.schema.ts, TournamentPairingsPage.utils.tsx, components/ConfirmPairingsDialog/**
Added a new Tournament Pairings Page with form-driven pairing entry, sortable grid, validation, table assignment, and confirmation dialog components and utilities.
src/components/generic/SortableGrid/** Added a generic sortable grid component, supporting drag-and-drop reordering with keyboard and pointer support, including styles and item component.
src/components/generic/Pulsar/** Added a new Pulsar component with animated status indicator and styles.
src/components/generic/Warning/** Added a new Warning component for displaying warning messages with icon and styles.
src/components/generic/Table/** Extended the Table component and types to support passing row index to cell renderers.
src/components/generic/InfoPopover/InfoPopover.tsx, .module.scss Added orientation prop for popover placement and set a max-width for content.
src/components/ConfirmationDialog/ConfirmationDialog.tsx, .hooks.ts, .module.scss Removed warnings prop and related UI, added disablePadding prop, updated internal structure and types.
src/modals.ts Updated useModal to accept optional key, auto-generating an ID if not provided.
src/pages/TournamentDetailPage/components/TournamentPairingsCard/TournamentPairingsCard.tsx Integrated the new round configuration dialog, updated round selection and empty state logic.
src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx Added support for competitor creation dialog in active tournaments with no current round.
src/components/TournamentContextMenu/** Added dialog for round configuration, start/end round actions, and corresponding hooks and ref handling.
src/settings.ts, src/routes.tsx Replaced the advance round route with the new pairings route and updated route configuration.
src/services/tournamentPairings.ts, src/services/tournaments.ts Added mutation hooks for creating pairings and starting/ending rounds; removed or renamed obsolete hooks.
src/api.ts Updated exports: added ShallowTournamentPairing, removed UnassignedTournamentPairing.
convex/_model/tournamentPairings/** Refactored pairing types and helpers, removed table assignment helper, added createTournamentPairings mutation, updated exports and queries.
convex/_model/tournaments/**, convex/tournaments.ts Renamed round mutation functions (open/closestart/end), updated triggers and exports, removed pairing assignment from round start.
convex/common/errors.ts Reorganized and expanded pairing-related error constants, adding detailed pairing validation errors.
convex/_model/tournamentTimers/mutations/createTournamentTimer.ts Changed timer initialization logic to start immediately instead of paused.
convex/_model/fowV4/aggregateFowV4TournamentData.ts Removed filtering of players/competitors with zero games played from aggregation results.
package.json Removed edmonds-blossom and its type definitions from dependencies.
src/style/_variables.scss Added blue color palette and info color CSS variables.
src/pages/TournamentDetailPage/index.ts, src/pages/TournamentPairingsPage/index.ts Added or updated index re-exports for detail and pairings pages.
src/components/TournamentPairingRow/TournamentPairingRow.tsx,
TournamentPairingRow.utils.tsx
Removed support for unassigned/draft pairing types, updated prop types and utility logic.
src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts Ensured default player array always matches competitor size.
convex/_model/tournamentCompetitors/_helpers/**,
queries/getTournamentCompetitors*.ts
Added/updated sorting helpers, competitor deepening, and ranking inclusion logic.
convex/_model/tournamentPairings/_helpers/** Renamed pairing types, added sorting helpers for pairs and by table, removed table assignment logic.
convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts Refactored draft pairing generation, added schema/type export, standardized output structure.
convex/_model/tournamentPairings/index.ts Updated type and function exports to match new pairing workflow, removed obsolete exports.
convex/tournamentPairings.ts Added mutation export for creating tournament pairings.
convex/_model/tournaments/README.md Updated lifecycle trigger names and order in documentation.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-07-08T18_46_27_601Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🔭 Outside diff range comments (2)
src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx (1)

84-95: Add key props to existing button elements.

For consistency, the existing button elements in the array should also have key props.

        return [
-          <Button onClick={openTournamentCreateTeamDialog}>
+          <Button key="new-team" onClick={openTournamentCreateTeamDialog}>
            <UserPlus />New Team
          </Button>,
        ];
      }
      return [
-        <Button onClick={handleRegister}>
+        <Button key="register" onClick={handleRegister}>
          <UserPlus />Register
        </Button>,
      ];
src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx (1)

7-11: Rename isUnassignedPairingInput to match the returned type

No references to UnassignedTournamentPairing remain in the codebase, and the guard now narrows to DraftTournamentPairing. To avoid confusion, update the function name (and any related imports/tests) accordingly:

• File: src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx (lines 7–11)

-export function isUnassignedPairingInput(pairing: unknown): pairing is DraftTournamentPairing {
+export function isDraftTournamentPairingInput(pairing: unknown): pairing is DraftTournamentPairing {
     return typeof pairing === 'object' &&
       pairing !== null &&
       'tournamentCompetitor0Id' in pairing;
}
🧹 Nitpick comments (16)
src/style/_variables.scss (1)

75-81: Expose complementary “info” tokens for visual parity

You introduced --text-color-blue and --bg-blue, but there are no matching border or variant tokens (e.g. --border-color-blue, --primary-info-*, etc.). Down-stream components may fall back to gray, leading to inconsistent styling. Adding the missing counterparts keeps the API symmetrical with danger, warning, and success variants.

src/components/generic/InfoPopover/InfoPopover.module.scss (1)

32-34: Nit: consider a CSS variable for the max-width

Hard-coding 20rem works, but exposing it as --popover-max-width (defaulting to 20rem) would allow design adjustments without recompiling Sass.

-    max-width: 20rem;
+    max-width: var(--popover-max-width, 20rem);
src/components/generic/SortableGrid/SortableGrid.module.scss (1)

1-5: Optional: parameterise gap to match spacing scale

The fixed 0.5rem gap breaks if the design system spacing scale changes. Using an existing CSS variable (e.g. --grid-gap-xs) or a Sass variable keeps the grid aligned with global spacing decisions.

src/components/generic/Pulsar/index.ts (1)

1-3: Export the props type for easier consumption

Down-stream code often needs the component’s props type for generics or React.ComponentProps<typeof Pulsar>. Re-exporting it here streamlines imports.

 export {
   Pulsar,
+  type PulsarProps,
 } from './Pulsar';
src/components/generic/SortableGrid/components/index.ts (1)

1-3: Consider aggregating related exports for better DX

SortableItem is only reachable via a deep path (.../SortableGrid/components). Re-exporting it from src/components/generic/SortableGrid/index.ts as well would let consumers import both SortableGrid and SortableItem from the same module barrel, avoiding brittle relative paths.

 // src/components/generic/SortableGrid/index.ts
 export {
   SortableGrid,
+  SortableItem,
 } from './SortableGrid';
convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts (1)

29-37: Consider extracting sorting logic into a dedicated helper.

The inline sorting logic is correct and properly handles null table values. However, the AI summary mentions a sortPairingsByTable utility was introduced. For consistency with the sorting refactoring pattern used in other queries (like sortTournamentCompetitorsByName), consider extracting this logic into a dedicated helper function.

-  return deepTournamentPairings.filter(notNullOrUndefined).sort((a, b) => {
-    if (a.table === null) {
-      return 1;
-    }
-    if (b.table === null) {
-      return -1;
-    }
-    return a.table - b.table;
-  });
+  return deepTournamentPairings.filter(notNullOrUndefined).sort(sortPairingsByTable);
src/components/TournamentContextMenu/TournamentContextMenu.tsx (1)

89-91: Address TODO comments for confirmation dialogs.

Consider implementing confirmation dialogs for the "Delete" and "Publish" actions to maintain consistency with the new "Configure Round" confirmation pattern.

Would you like me to help implement confirmation dialogs for the Delete and Publish actions to maintain consistency?

src/components/generic/Pulsar/Pulsar.tsx (1)

25-28: Clean up commented code.

The commented preventDefault() line should either be removed if not needed or uncommented with a comment explaining why it's necessary.

-    onClick={onClick ? (e) => {
-      // e.preventDefault();
-      onClick(e);
-    } : undefined}
+    onClick={onClick ? (e) => {
+      onClick(e);
+    } : undefined}

Or if preventDefault is needed:

-    onClick={onClick ? (e) => {
-      // e.preventDefault();
-      onClick(e);
-    } : undefined}
+    onClick={onClick ? (e) => {
+      e.preventDefault(); // Prevent default behavior when pulsar is clicked
+      onClick(e);
+    } : undefined}
src/components/generic/Pulsar/Pulsar.module.scss (1)

23-25: Consider making dimensions configurable.

The fixed 40px dimensions work for the current use case, but consider making this configurable via CSS custom properties for better reusability.

-    width: 40px;
-    height: 40px;
+    width: var(--pulsar-size, 40px);
+    height: var(--pulsar-size, 40px);
src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx (1)

79-79: Remove redundant variable declaration.

The isOrganizer variable is already declared at line 51 and is accessible in this scope.

-    const isOrganizer = user && tournament.organizerUserIds.includes(user._id);
convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (1)

41-43: Competitor filtering logic could be more efficient.

The nested find operation inside filter results in O(n²) complexity. Consider using a Set for better performance.

-  const activeCompetitors = rankedCompetitors.filter(({ id }) => (
-    !!competitors.find((c) => c._id === id && c.active)
-  ));
+  const activeCompetitorIds = new Set(competitors.filter(c => c.active).map(c => c._id));
+  const activeCompetitors = rankedCompetitors.filter(({ id }) => activeCompetitorIds.has(id));
convex/_model/tournamentPairings/mutations/createTournamentPairings.ts (1)

30-41: Review error message consistency for pairing creation context.

The error messages reference "CANNOT_OPEN_ROUND_ON_*" but this function is specifically about creating pairings, not opening rounds. Consider using more specific error messages like "CANNOT_CREATE_PAIRINGS_ON_DRAFT_TOURNAMENT" for better clarity.

src/components/generic/SortableGrid/components/SortableItem.tsx (1)

37-43: Consider extracting complex getNewIndex logic.

The getNewIndex function contains array swapping logic that could be extracted for better readability and testability.

Consider extracting this logic:

+const getNewIndexForSwap = (id: UniqueIdentifier, items: UniqueIdentifier[], activeIndex: number, overIndex: number) => 
+  arraySwap(items, activeIndex, overIndex).indexOf(id);

 const {
   attributes,
   isDragging,
   listeners,
   setNodeRef,
   transform,
   transition,
 } = useSortable({
   id,
-  getNewIndex: ({
-    id,
-    items,
-    activeIndex,
-    overIndex,
-  }) => arraySwap(items, activeIndex, overIndex).indexOf(id),
+  getNewIndex: ({ id, items, activeIndex, overIndex }) => 
+    getNewIndexForSwap(id, items, activeIndex, overIndex),
 });
src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx (1)

47-77: Consider extracting validation logic for better maintainability.

The validation logic inside the imperative handle is complex and could benefit from extraction into a separate validation function for better testability and maintainability.

Consider extracting the validation logic:

+const validateTournamentRound = (
+  activeCompetitors: TournamentCompetitor[],
+  tournament: Tournament,
+  toast: ToastApi
+): boolean => {
+  if (activeCompetitors.length < 2) {
+    toast.error('Cannot Configure Round', {
+      description: 'Please ensure at least 2 competitors are active.',
+    });
+    return false;
+  }
+  // ... rest of validation logic
+  return true;
+};

 useImperativeHandle(ref, () => ({
   open: () => {
-    if (activeCompetitors.length < 2) {
-      return toast.error('Cannot Configure Round', {
-        description: 'Please ensure at least 2 competitors are active.',
-      });
-    }
-    // ... rest of validation
+    if (!validateTournamentRound(activeCompetitors, tournament, toast)) {
+      return;
+    }
     
     if (warnings.length) {
       open();
     } else {
       proceed();
     }
   },
 }));
src/components/generic/SortableGrid/SortableGrid.tsx (1)

88-88: Use a more idiomatic approach for creating repeated array values.

Instead of using underscore for unused parameters, use a cleaner approach.

-    gridTemplateColumns: Array.from({ length: columns }).map((_) => '1fr').join(' '),
+    gridTemplateColumns: Array.from({ length: columns }, () => '1fr').join(' '),
src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx (1)

27-31: Complete or remove empty JSDoc comments.

Multiple functions have empty JSDoc comments that should either be completed with proper documentation or removed.

Either add meaningful documentation:

/**
- * 
- * @param pairings 
- * @returns 
+ * Flattens an array of tournament pairings into a single array of competitor IDs.
+ * @param pairings - Array of tournament pairing objects
+ * @returns Array of competitor IDs with 'bye' for null competitors
 */

Or remove the empty JSDoc blocks entirely.

Also applies to: 45-50, 129-134

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85245b1 and 0279b7c.

⛔ Files ignored due to path filters (2)
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (92)
  • convex/_model/fowV4/aggregateFowV4TournamentData.ts (1 hunks)
  • convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (3 hunks)
  • convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (1 hunks)
  • convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts (2 hunks)
  • convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts (2 hunks)
  • convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts (3 hunks)
  • convex/_model/tournamentPairings/_helpers/generateTableAssignments.ts (0 hunks)
  • convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts (1 hunks)
  • convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts (1 hunks)
  • convex/_model/tournamentPairings/index.ts (4 hunks)
  • convex/_model/tournamentPairings/mutations/createTournamentPairings.ts (1 hunks)
  • convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (2 hunks)
  • convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts (1 hunks)
  • convex/_model/tournaments/mutations/openTournamentRound.ts (0 hunks)
  • convex/tournamentPairings.ts (2 hunks)
  • package.json (0 hunks)
  • src/api.ts (1 hunks)
  • src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts (1 hunks)
  • src/components/ConfirmationDialog/ConfirmationDialog.module.scss (1 hunks)
  • src/components/ConfirmationDialog/ConfirmationDialog.tsx (3 hunks)
  • src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts (1 hunks)
  • src/components/TournamentContextMenu/TournamentContextMenu.tsx (4 hunks)
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.module.scss (1 hunks)
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx (1 hunks)
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.utils.tsx (1 hunks)
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/index.ts (1 hunks)
  • src/components/TournamentPairingRow/TournamentPairingRow.tsx (1 hunks)
  • src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx (2 hunks)
  • src/components/TournamentPairingsGrid/Draggable/Draggable.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/Draggable/Draggable.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/Draggable/index.ts (0 hunks)
  • src/components/TournamentPairingsGrid/Droppable/Droppable.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/Droppable/Droppable.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/Droppable/index.ts (0 hunks)
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/index.ts (0 hunks)
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.utils.ts (0 hunks)
  • src/components/TournamentPairingsGrid/PairingsGridRow/index.ts (0 hunks)
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.types.ts (0 hunks)
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.utils.ts (0 hunks)
  • src/components/TournamentPairingsGrid/index.ts (0 hunks)
  • src/components/generic/InfoPopover/InfoPopover.module.scss (1 hunks)
  • src/components/generic/InfoPopover/InfoPopover.tsx (3 hunks)
  • src/components/generic/Pulsar/Pulsar.module.scss (1 hunks)
  • src/components/generic/Pulsar/Pulsar.tsx (1 hunks)
  • src/components/generic/Pulsar/index.ts (1 hunks)
  • src/components/generic/SortableGrid/SortableGrid.module.scss (1 hunks)
  • src/components/generic/SortableGrid/SortableGrid.tsx (1 hunks)
  • src/components/generic/SortableGrid/components/SortableItem.module.scss (1 hunks)
  • src/components/generic/SortableGrid/components/SortableItem.tsx (1 hunks)
  • src/components/generic/SortableGrid/components/index.ts (1 hunks)
  • src/components/generic/SortableGrid/index.ts (1 hunks)
  • src/components/generic/Table/Table.tsx (1 hunks)
  • src/components/generic/Table/Table.types.ts (1 hunks)
  • src/components/generic/Table/TableCell.tsx (2 hunks)
  • src/components/generic/Table/TableRow.tsx (2 hunks)
  • src/components/generic/Warning/Warning.module.scss (1 hunks)
  • src/components/generic/Warning/Warning.tsx (1 hunks)
  • src/components/generic/Warning/index.ts (1 hunks)
  • src/modals.ts (2 hunks)
  • src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.hooks.ts (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.tsx (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.module.scss (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.tsx (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.utils.ts (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/index.ts (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.module.scss (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.tsx (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/index.ts (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/index.ts (0 hunks)
  • src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx (5 hunks)
  • src/pages/TournamentDetailPage/index.ts (1 hunks)
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.module.scss (1 hunks)
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (1 hunks)
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx (1 hunks)
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx (1 hunks)
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss (1 hunks)
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx (1 hunks)
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts (1 hunks)
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/index.ts (1 hunks)
  • src/pages/TournamentPairingsPage/index.ts (1 hunks)
  • src/routes.tsx (2 hunks)
  • src/services/tournamentPairings.ts (2 hunks)
  • src/settings.ts (1 hunks)
  • src/style/_variables.scss (2 hunks)
💤 Files with no reviewable changes (33)
  • src/components/TournamentPairingsGrid/Draggable/index.ts
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/index.ts
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.utils.ts
  • src/pages/TournamentAdvanceRoundPage/index.ts
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.module.scss
  • src/components/TournamentPairingsGrid/index.ts
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/index.ts
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.utils.ts
  • src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss
  • src/components/TournamentPairingsGrid/Droppable/index.ts
  • package.json
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/index.ts
  • src/components/TournamentPairingsGrid/Draggable/Draggable.module.scss
  • src/components/TournamentPairingsGrid/PairingsGridRow/index.ts
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.module.scss
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.module.scss
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.types.ts
  • src/components/TournamentPairingsGrid/Droppable/Droppable.module.scss
  • convex/_model/tournaments/mutations/openTournamentRound.ts
  • src/components/TournamentPairingsGrid/Draggable/Draggable.tsx
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.tsx
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.tsx
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.tsx
  • src/components/TournamentPairingsGrid/Droppable/Droppable.tsx
  • src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.hooks.ts
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.module.scss
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.module.scss
  • convex/_model/tournamentPairings/_helpers/generateTableAssignments.ts
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.tsx
  • src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx
  • src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.tsx
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.utils.ts
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.tsx
🧰 Additional context used
🧬 Code Graph Analysis (19)
convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (2)
convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (1)
  • DeepTournamentCompetitor (46-46)
src/api.ts (1)
  • DeepTournamentCompetitor (33-33)
src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts (1)
src/modals.ts (1)
  • useModal (28-37)
src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (2)
src/api.ts (1)
  • TournamentCompetitorId (34-34)
src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts (3)
  • createSchema (6-41)
  • FormData (43-43)
  • getDefaultValues (50-56)
src/components/generic/Table/TableCell.tsx (1)
src/components/generic/Table/Table.types.ts (1)
  • RowData (3-3)
convex/_model/tournamentPairings/index.ts (2)
src/api.ts (1)
  • ShallowTournamentPairing (39-39)
convex/_generated/dataModel.d.ts (1)
  • Doc (30-33)
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx (6)
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts (1)
  • assignTables (4-74)
src/components/generic/Table/Table.types.ts (1)
  • ColumnDef (5-13)
src/components/TournamentPairingRow/TournamentPairingRow.tsx (1)
  • TournamentPairingRow (17-40)
src/components/ConfirmationDialog/ConfirmationDialog.tsx (1)
  • ConfirmationDialog (29-73)
src/components/generic/Table/Table.tsx (1)
  • Table (16-32)
src/components/generic/Warning/Warning.tsx (1)
  • Warning (12-25)
convex/_model/fowV4/aggregateFowV4TournamentData.ts (1)
convex/_model/fowV4/flattenFowV4StatMap.ts (1)
  • flattenFowV4StatMap (14-33)
convex/tournamentPairings.ts (1)
convex/_model/tournamentPairings/mutations/createTournamentPairings.ts (1)
  • createTournamentPairings (20-56)
convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts (1)
convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (1)
  • sortTournamentCompetitorsByName (3-20)
convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts (2)
convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (1)
  • deepenTournamentCompetitor (17-41)
convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (1)
  • sortTournamentCompetitorsByName (3-20)
src/components/generic/Table/Table.tsx (1)
src/components/generic/Table/TableRow.tsx (1)
  • TableRow (19-44)
convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts (1)
convex/_model/common/_helpers/notNullOrUndefined.ts (1)
  • notNullOrUndefined (1-3)
convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts (3)
convex/_model/tournamentPairings/index.ts (3)
  • TournamentPairingDeep (18-18)
  • ShallowTournamentPairing (13-13)
  • DraftTournamentPairing (32-32)
src/api.ts (3)
  • TournamentPairingDeep (40-40)
  • ShallowTournamentPairing (39-39)
  • DraftTournamentPairing (86-86)
convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (1)
  • DraftTournamentPairing (14-14)
src/components/generic/Table/TableRow.tsx (2)
src/components/generic/Table/Table.types.ts (1)
  • RowData (3-3)
src/components/generic/Table/TableCell.tsx (1)
  • TableCell (14-43)
src/routes.tsx (3)
src/settings.ts (1)
  • PATHS (10-24)
src/pages/TournamentPairingsPage/index.ts (1)
  • TournamentPairingsPage (2-2)
src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx (1)
  • TournamentPairingsPage (54-263)
convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (1)
convex/_generated/dataModel.d.ts (1)
  • Id (48-49)
convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts (1)
convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts (1)
  • CompetitorPair (10-10)
src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx (1)
src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (1)
  • TournamentPairingFormItem (17-17)
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts (4)
src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (1)
  • TournamentPairingFormItem (17-17)
convex/_model/tournamentPairings/index.ts (1)
  • DraftTournamentPairing (32-32)
convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (1)
  • DraftTournamentPairing (14-14)
src/api.ts (1)
  • DraftTournamentPairing (86-86)
🪛 Biome (1.9.4)
src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx

[error] 35-35: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx

[error] 98-98: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

🔇 Additional comments (84)
src/style/_variables.scss (1)

29-34: Blue palette import mirrors existing pattern – verify bundle size impact

@use-ing four additional Radix blue stylesheets increases the amount of generated CSS. If the blue palette is only used for a handful of utility variables, consider importing just the required tokens (or tree-shaking via PostCSS) to avoid bloating the final bundle.

src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.module.scss (1)

1-5: LGTM – idiomatic namespaced mixin usage

The module correctly namespaces the flex utilities and applies them via flex.column; no issues spotted.

src/components/generic/SortableGrid/index.ts (1)

1-3: Path looks correct – no issues spotted

The re-export is straightforward and matches the file structure.

src/pages/TournamentDetailPage/index.ts (1)

1-3: Barrel export is fine, but keep an eye on tree-shaking

If TournamentDetailPage is large, ensure it’s lazily imported elsewhere (e.g., via React.lazy / dynamic import) so this barrel doesn’t accidentally pull it into initial bundles.

src/pages/TournamentPairingsPage/index.ts (1)

1-3: LGTM

Simple, correct re-export; nothing to add.

src/components/generic/Warning/index.ts (1)

1-2: Nice touch exporting the prop type

Good practice to expose WarningProps alongside the component – encourages proper typing by consumers.

src/settings.ts (1)

22-22: ✅ Approved: Route change to tournamentPairings is complete with no leftover references

  • Verified with ripgrep across *.ts, *.tsx, *.js, and *.jsx files: no occurrences of tournamentAdvance or /tournaments/:id/advance remain.
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/index.ts (1)

3-3: LGTM! Standard TypeScript export pattern.

The addition of the props type export follows established conventions and enables proper type checking for consumers of this component.

convex/_model/fowV4/aggregateFowV4TournamentData.ts (1)

134-142: Downstream impact verified – no further action required.

All usages of aggregateFowV4TournamentData (notably in getTournamentRankings.ts) were inspected and there are no other filters or logic assuming gamesPlayed > 0. The ranking function handles zero‐game participants correctly (they simply sort to the bottom).

src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/index.ts (1)

1-2: LGTM! Standard component export pattern.

The export statements follow established TypeScript conventions and properly expose both the component and its props type.

src/services/tournamentPairings.ts (2)

2-2: LGTM! Import addition follows established patterns.

The addition of createMutationHook to the imports is correct and necessary for the new mutation hook export.


13-13: LGTM! Mutation hook follows established conventions.

The new useCreateTournamentPairings hook follows the established naming convention and pattern used throughout the codebase for mutation hooks.

src/api.ts (1)

39-39: LGTM! Type export aligns with refactoring.

The addition of ShallowTournamentPairing export is consistent with the broader refactoring to remove unassigned pairing constructs and use shallow document representations.

convex/tournamentPairings.ts (2)

1-1: LGTM! Proper import addition for mutation functionality.

The addition of mutation import enables the new mutation export functionality.


24-27: Verify that TODO validation items are addressed.

The mutation implementation looks correct with proper authorization and basic validation. However, the implementation in convex/_model/tournamentPairings/mutations/createTournamentPairings.ts (lines 28-33) contains several TODO comments for critical validation:

  • Validation for missing or invalid pairings
  • Validation for competitor count and active status
  • Validation for duplicate round pairings
  • Validation for proper player counts per competitor

These validations are important for data integrity and should be implemented before this feature is deployed to production.

src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts (1)

9-9: LGTM! Improved API flexibility with optional ID.

Making the id parameter optional aligns with the pattern in useModal (src/modals.ts:28-37) and improves the hook's usability by auto-generating IDs when not specified. This change is backwards compatible with existing code.

src/routes.tsx (3)

29-29: LGTM! Simplified import path.

The import path simplification for TournamentDetailPage improves code organization.


31-31: LGTM! Component replacement aligns with refactoring.

The replacement of TournamentAdvanceRoundPage with TournamentPairingsPage is consistent with the broader refactoring described in the AI summary to replace the old advance round wizard with a new streamlined Tournament Pairings Page.


136-139: LGTM! Route configuration properly updated.

The route configuration correctly uses the new PATHS.tournamentPairings path and TournamentPairingsPage component. The relevant code snippets confirm the new component is properly implemented with tournament pairing setup and confirmation logic.

convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts (1)

3-3: LGTM: Clean refactoring to use centralized sorting logic.

The extraction of sorting logic into the sortTournamentCompetitorsByName helper improves code maintainability and ensures consistent sorting behavior across tournament competitor queries.

Also applies to: 12-12

src/components/generic/Table/Table.tsx (1)

27-27: LGTM: Proper index propagation to table components.

The addition of the index={i} prop correctly enables row index access throughout the table rendering pipeline, aligning with the TableRow component's interface.

convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts (3)

4-4: LGTM: Good refactoring to use centralized sorting logic.

The extraction of sorting logic into the sortTournamentCompetitorsByName helper maintains consistency with the sorting refactoring pattern used across the codebase.

Also applies to: 6-6, 28-28


10-10: LGTM: Well-implemented optional rankings feature.

The optional rankings functionality is properly implemented with appropriate null checking and conditional logic.

Also applies to: 20-23


24-27: LGTM: Proper integration of ranking results with competitor data.

The mapping logic correctly finds and includes ranking results for each competitor, handling cases where rankings might not be available.

src/components/TournamentPairingRow/TournamentPairingRow.tsx (1)

4-4: LGTM: Clean type cleanup removing unused pairing type.

The removal of UnassignedTournamentPairing from the import and props interface aligns with the broader refactoring that streamlines the pairing type system.

Also applies to: 12-12

src/modals.ts (2)

3-3: LGTM: UUID import added for automatic ID generation.

The uuid import is properly added to support the auto-generation feature in the useModal hook.


28-29: LGTM: Improved modal hook flexibility with automatic ID generation.

The changes effectively enhance the useModal hook by:

  • Making the key parameter optional
  • Auto-generating unique IDs when no key is provided
  • Maintaining backward compatibility

This is a clean improvement that reduces boilerplate for consumers while preserving the ability to use custom IDs when needed.

src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.utils.tsx (2)

18-18: Verify the round calculation logic change.

The round calculation was changed from (tournament.currentRound ?? 0) + 1 to (tournament.lastRound ?? -1) + 2. This represents a significant change in how the current round is determined.

Please ensure this aligns with the tournament data model changes and that the logic correctly calculates the round number for the pairing process.


19-19: LGTM: Clean refactor to destructure competitor arrays.

The refactor to destructure active and inactive arrays directly from sortCompetitorsByActive improves readability and eliminates the need for property access throughout the function. The logic remains functionally identical.

Also applies to: 21-21, 25-25, 27-27, 33-33

convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (1)

1-21: LGTM: Well-structured sorting utility with proper fallback handling.

The sorting function is well-implemented with:

  • Clear priority order (teamName → familyName → username → empty string)
  • Proper null/undefined handling with optional chaining
  • Locale-aware string comparison using localeCompare
  • Clean helper function structure

The implementation correctly handles all edge cases and provides consistent sorting behavior across the codebase.

src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts (1)

52-55: LGTM: Improved player array initialization with proper sizing.

The change ensures the players array always contains exactly competitorSize elements, which is crucial for:

  • Form validation consistency
  • UI rendering predictability
  • Proper handling of existing competitor data

The implementation correctly maps existing player data while providing sensible defaults (active: true, userId: '') for new positions.

convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts (1)

1-29: LGTM: Robust table sorting with proper null handling.

The sorting utility is well-implemented with:

  • Generic type constraint supporting multiple pairing types (AnyPairing)
  • Proper null/undefined handling (placing them at the end)
  • Clean sorting logic for valid table numbers
  • Consistent return values for comparison function

The implementation correctly handles all edge cases and provides predictable sorting behavior for tournament pairings.

src/components/generic/Table/TableCell.tsx (1)

11-11: LGTM! Clean implementation of index prop.

The addition of the required index prop and its usage in the renderCell function call is well-implemented and maintains type safety.

Also applies to: 17-17, 30-30

src/components/generic/Table/TableRow.tsx (1)

16-16: Clarify the dual index handling logic.

The component now handles two different indices:

  1. The index prop passed to TableRow (optional, defaults to -1)
  2. The index i extracted from the row tuple on line 35

In line 39, you're passing the index prop to TableCell, not the row's own index i. Please verify this is the intended behavior, as it could be confusing to have two different index concepts.

Also applies to: 23-23, 39-39

src/components/generic/Warning/Warning.module.scss (1)

1-46: LGTM! Well-structured SCSS module.

The Warning component styles are well-organized with:

  • Proper use of utility mixins
  • Clean CSS Grid layout with semantic grid areas
  • Consistent use of CSS variables for theming
  • Logical spacing and sizing

The grid template effectively separates icon, header, and body content areas.

src/components/generic/InfoPopover/InfoPopover.tsx (1)

13-13: LGTM! Clean implementation of orientation feature.

The orientation prop addition is well-implemented:

  • Properly typed with specific string literals
  • Sensible default value of 'vertical'
  • Correctly maps to the side prop for dynamic positioning

This provides good flexibility for popover placement.

Also applies to: 22-22, 39-39

src/components/generic/Warning/Warning.tsx (1)

1-25: LGTM! Clean and well-structured warning component.

The implementation follows React best practices with proper TypeScript interfaces, CSS modules, and the clsx utility for conditional styling. The component is reusable and maintains consistency with the generic component pattern used throughout the codebase.

src/components/TournamentContextMenu/TournamentContextMenu.tsx (5)

1-1: Good addition of useRef import for dialog management.

The import is correctly added to support the new confirmation dialog functionality.


9-10: Proper import and typing for the confirmation dialog.

The imports are correctly structured with both the component and its handle type, following TypeScript best practices.


78-78: Correctly typed ref for imperative dialog control.

The ref is properly typed with ConfirmConfigureRoundDialogHandle to enable imperative control of the dialog.


105-107: Improved UX with confirmation dialog.

The change from direct navigation to showing a confirmation dialog improves the user experience by providing validation and confirmation before configuring a round.


130-138: Proper component structure with fragment wrapper.

The fragment wrapper correctly accommodates both the popover menu and the confirmation dialog, maintaining clean component structure.

convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (3)

1-1: Good addition of Id import for proper typing.

The Id import is correctly added to support the new results parameter typing with tournament competitor IDs.


20-25: Well-structured optional results parameter.

The optional results parameter is well-typed with a clear interface that includes all necessary ranking and match data. The optional nature maintains backwards compatibility while extending functionality.


38-38: Correct spread operator usage for results integration.

The spread operator correctly integrates the optional results data into the returned object, maintaining the existing structure while adding the new fields when provided.

src/components/generic/Pulsar/Pulsar.tsx (4)

13-19: Excellent use of forwardRef with proper TypeScript typing.

The forwardRef implementation is correct and allows parent components to access the underlying DOM element while maintaining proper TypeScript typing.


5-11: Well-designed props interface with sensible defaults.

The props interface is comprehensive and includes all necessary customization options with appropriate types and sensible defaults.


29-35: Good use of inline styles for dynamic sizing and conditional rendering.

The inline styles for dynamic sizing and conditional pulse animation are appropriate for this type of visual component where the size needs to be configurable.


38-38: Proper displayName setting for debugging.

Setting the displayName is good practice for debugging and development tools.

src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss (2)

1-4: Good use of SCSS imports for design system consistency.

The imports properly utilize the design system's variables, flex mixins, and text utilities, ensuring consistency across the application.


5-49: Well-structured SCSS with proper BEM naming convention.

The styles follow a clear BEM-like naming convention with the component name prefix. The use of CSS custom properties, mixins, and nested selectors creates maintainable and consistent styles that integrate well with the design system.

src/components/ConfirmationDialog/ConfirmationDialog.module.scss (1)

8-14: Clean and modern SCSS implementation.

The use of data attributes for conditional styling and CSS custom properties for padding values follows modern CSS best practices. The flex column layout is appropriate for dialog body content.

src/components/generic/Pulsar/Pulsar.module.scss (1)

71-81: Well-designed pulse animation.

The keyframe animation provides smooth scaling and fading effects. The scale factor of 3 with the comment explaining the visual effect is helpful for maintainability.

src/pages/TournamentPairingsPage/TournamentPairingsPage.module.scss (2)

24-32: Well-structured CSS Grid implementation.

The grid layout with named areas provides excellent organization for the complex tournament pairings interface. The use of CSS custom properties for consistent spacing is a good practice.


68-76: Proper button styling within grid context.

The full-width button styling within the grid item ensures consistent visual hierarchy and proper touch targets.

src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (2)

6-9: Type casting pattern follows existing conventions.

The union type with string transformation to TournamentCompetitorId is consistent with patterns used elsewhere in the codebase, as seen in the relevant code snippets.


11-15: Schema structure supports form requirements well.

The draft tournament pairing schema properly handles nullable values for table assignments and competitor IDs, which aligns with the pairing workflow where these values start as null and get assigned through user interaction.

src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts (5)

4-9: The function signature and return type look correct.

The function takes the appropriate inputs and returns the expected DraftTournamentPairing[] type. The intersection with playedTables is properly typed.


15-15: Efficient table pool creation using array indices.

The approach of creating available tables using Array.from({ length: tableCount }, (_, i) => i) is clean and efficient. The filtering logic correctly excludes already used tables.


65-73: Correct sorting implementation for table assignments.

The sorting logic correctly places null tables at the end and sorts numbered tables in ascending order. This ensures a consistent ordering for the UI.


38-44: Potential bug: table assignment from filtered pool.

The logic finds an available table but doesn't handle the case where table could be null when removing from the pool. This could cause issues if no tables are available.

-        const table = availableTables.find((table) => !playedTables.includes(table)) ?? availableTables[0] ?? null;
+        const table = availableTables.find((table) => !playedTables.includes(table)) ?? availableTables[0] ?? null;

-        // Remove assigned table from available pool:
-        const index = availableTables.indexOf(table);
-        if (index > -1) {
-          availableTables.splice(index, 1);
-        }
+        // Remove assigned table from available pool:
+        if (table !== null) {
+          const index = availableTables.indexOf(table);
+          if (index > -1) {
+            availableTables.splice(index, 1);
+          }
+        }

Likely an incorrect or invalid review comment.


12-12: No undefined case for table
The TournamentPairingFormItem schema defines table as z.union([z.number(), z.null()]), so it’s always present as either a number or null—never undefined. Your existing filter

const usedTables = new Set(
  pairings
    .filter((p) => p.table !== null && p.table > -1)
    .map((p) => p.table)
);

already safely excludes null and negative values.

Likely an incorrect or invalid review comment.

convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts (2)

10-10: Type rename improves semantic clarity.

The rename from DraftTournamentPairing to CompetitorPair better reflects what this type represents - a pair of competitors rather than a full pairing object. This aligns with the function's purpose of generating competitor pairs.


26-26: Function signatures correctly updated.

Both generateDraftPairings and recursivePair function signatures have been updated to use the new CompetitorPair type consistently.

Also applies to: 55-55

convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (4)

6-6: New imports properly support the refactored functionality.

The imports for getTournamentRankings, TournamentCompetitorRanked, and sortCompetitorPairs are correctly added to support the new implementation.

Also applies to: 10-10


13-14: Schema-based type definition follows best practices.

Using v.object(uniqueFields) to define the schema and Infer to extract the type is a good pattern for type safety and consistency.


44-50: Simplified competitor ordering logic.

The refactored approach using a single orderedCompetitors array and pushing based on method is cleaner than the previous implementation.


51-57: Comprehensive output formatting with proper sorting.

The chain of operations (generate → sort → map) creates a consistent output format. The mapping correctly includes all required fields with appropriate defaults.

src/components/ConfirmationDialog/ConfirmationDialog.tsx (2)

26-26: New disablePadding prop enhances layout flexibility.

The addition of the disablePadding prop with a sensible default of false allows for better control over dialog content spacing without breaking existing usage.

Also applies to: 38-38


59-61: Simplified body container with data attribute styling.

The new implementation uses a CSS class with a data attribute to control padding, which is more maintainable than conditional rendering. This approach works well with the new Warning component mentioned in the AI summary.

src/components/generic/SortableGrid/components/SortableItem.module.scss (5)

1-8: Well-structured imports and shadow definitions.

The SCSS imports are properly organized and the shadow variables use modern CSS syntax with proper fallbacks. The calculated shadow based on scale is a nice touch for 3D depth perception.


14-17: Efficient 3D transforms using CSS custom properties.

The use of CSS custom properties for transforms allows for smooth JavaScript-controlled animations. The translate3d approach enables hardware acceleration for better performance.


52-52: Good accessibility consideration with tap highlight removal.

The -webkit-tap-highlight-color: transparent prevents unwanted highlight colors on touch devices, which is appropriate for draggable elements.


59-66: Smooth overlay state transitions with proper visual feedback.

The overlay state provides clear visual feedback with scale transform, cursor changes, and enhanced shadows. The animation timing function creates a pleasing bounce effect.


70-80: Well-implemented animation keyframes.

The pop animation provides smooth transitions between states with matching transform and shadow values. The cubic-bezier timing function creates a natural feel.

src/components/generic/SortableGrid/components/SortableItem.tsx (2)

45-53: Ensure cursor cleanup on component unmount.

The current implementation correctly returns a cleanup function, but consider adding a safety check to ensure the cursor is always reset even if the component unmounts unexpectedly.

The cleanup logic is correctly implemented with the return function in useEffect.


21-78: Well-implemented sortable component with proper performance optimizations.

The component correctly uses memo and forwardRef for performance, properly handles the drag state, and manages the overlay functionality effectively.

convex/_model/tournamentPairings/index.ts (3)

3-3: LGTM - Clean import addition.

Adding the Doc import alongside Id is appropriate for creating the new type alias.


49-53: Mutation exports correctly exposed

The createTournamentPairings mutation and its createTournamentPairingsArgs are properly exported in convex/_model/tournamentPairings/index.ts and consumed throughout the codebase:

  • src/services/tournamentPairings.ts – hook using api.tournamentPairings.createTournamentPairings
  • src/pages/TournamentPairingsPage/... – mutation invoked in the page component
  • convex/_generated/api.d.ts – mutation and args included in generated API types

No further changes required.


13-13: Verified ShallowTournamentPairing usage across the codebase

All occurrences of ShallowTournamentPairing have been updated to import from the new alias:

  • src/api.ts – correctly re-exports ShallowTournamentPairing from convex/_model/tournamentPairings
  • convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts – correctly imports ShallowTournamentPairing via the index

No outdated references remain.

src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx (2)

58-70: Validate player count logic is correct.

The validation logic for active players per competitor correctly checks both minimum and maximum constraints. The error messages are clear and actionable.


84-84: Round number calculation is correct as written.

The lastRound field is a 0-based index of the last completed round (using undefined ?? -1 for “no rounds yet”), and the UI adds 1 to get the upcoming round index and another 1 to convert to human-readable numbering. That is why everywhere you see:

  • In the confirmation dialog and warnings util: (tournament.lastRound ?? -1) + 2
  • In the context menu label: (lastRound ?? -1) + 2
  • In the backend mutation: (tournament.lastRound ?? -1) + 1 to set the next round index

These are all consistent: the backend uses +1 to compute the next round’s zero-based index, and the frontend uses +2 to display that as “Round 1”, “Round 2”, etc. No changes are required.

src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx (2)

3-5: Verify TournamentPairingFormItem import and usage.

The new import for TournamentPairingFormItem looks correct and aligns with the form-based approach mentioned in the summary.


19-21: Function signature update supports new form approach.

The updated function signature now supports the new TournamentPairingFormItem type, which aligns with the form-based pairing approach described in the summary.

Comment on lines +7 to +8
const aHasNull = a[0] === null || a[1] === null;
const bHasNull = b[0] === null || b[1] === null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix unnecessary null check for first element of CompetitorPair.

According to the CompetitorPair type definition, the first element is always TournamentCompetitorRanked (never null), and only the second element can be null. The check a[0] === null will never be true and is unnecessary.

-  const aHasNull = a[0] === null || a[1] === null;
-  const bHasNull = b[0] === null || b[1] === null;
+  const aHasNull = a[1] === null;
+  const bHasNull = b[1] === null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const aHasNull = a[0] === null || a[1] === null;
const bHasNull = b[0] === null || b[1] === null;
const aHasNull = a[1] === null;
const bHasNull = b[1] === null;
🤖 Prompt for AI Agents
In convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts at lines 7
to 8, remove the null checks for the first element of the pairs (a[0] and b[0])
since the first element is guaranteed to never be null. Only check if the second
element (a[1] and b[1]) is null to correctly reflect the CompetitorPair type
definition.

Comment on lines +25 to +29
export const getDefaultValues = (tournament?: Tournament | null): DeepPartial<FormData['pairings']> => tournament ? Array.from({ length: Math.ceil((tournament?.maxCompetitors ?? 24) / 2) }).map((_) => ({
table: -1,
tournamentCompetitor0Id: null,
tournamentCompetitor1Id: null,
})) : [];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Simplify the default values function and clarify magic numbers.

The ternary operator can be simplified, and magic numbers should be documented for clarity.

-export const getDefaultValues = (tournament?: Tournament | null): DeepPartial<FormData['pairings']> => tournament ? Array.from({ length: Math.ceil((tournament?.maxCompetitors ?? 24) / 2) }).map((_) => ({
-  table: -1,
-  tournamentCompetitor0Id: null,
-  tournamentCompetitor1Id: null,
-})) : [];
+export const getDefaultValues = (tournament?: Tournament | null): DeepPartial<FormData['pairings']> => {
+  if (!tournament) return [];
+  
+  const DEFAULT_MAX_COMPETITORS = 24; // Fallback for tournaments without maxCompetitors
+  const UNASSIGNED_TABLE = -1; // Indicates table not yet assigned
+  
+  const maxCompetitors = tournament.maxCompetitors ?? DEFAULT_MAX_COMPETITORS;
+  const pairingsCount = Math.ceil(maxCompetitors / 2);
+  
+  return Array.from({ length: pairingsCount }).map(() => ({
+    table: UNASSIGNED_TABLE,
+    tournamentCompetitor0Id: null,
+    tournamentCompetitor1Id: null,
+  }));
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getDefaultValues = (tournament?: Tournament | null): DeepPartial<FormData['pairings']> => tournament ? Array.from({ length: Math.ceil((tournament?.maxCompetitors ?? 24) / 2) }).map((_) => ({
table: -1,
tournamentCompetitor0Id: null,
tournamentCompetitor1Id: null,
})) : [];
export const getDefaultValues = (
tournament?: Tournament | null
): DeepPartial<FormData['pairings']> => {
if (!tournament) return [];
const DEFAULT_MAX_COMPETITORS = 24; // Fallback for tournaments without maxCompetitors
const UNASSIGNED_TABLE = -1; // Indicates table not yet assigned
const maxCompetitors = tournament.maxCompetitors ?? DEFAULT_MAX_COMPETITORS;
const pairingsCount = Math.ceil(maxCompetitors / 2);
return Array.from({ length: pairingsCount }).map(() => ({
table: UNASSIGNED_TABLE,
tournamentCompetitor0Id: null,
tournamentCompetitor1Id: null,
}));
};
🤖 Prompt for AI Agents
In src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts around
lines 25 to 29, simplify the getDefaultValues function by avoiding the ternary
operator and instead using a clearer conditional structure. Replace the magic
number 24 with a named constant or add a comment explaining its significance to
improve code clarity and maintainability.

Comment on lines +25 to +35
const { maxCompetitors } = useTournament();

const assignedPairings = assignTables(pairings.filter((pairing) => (
pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id
)).map((pairing) => ({
...pairing,
playedTables: Array.from(new Set([
...competitors.find((c) => c._id === pairing.tournamentCompetitor0Id)?.playedTables ?? [],
...competitors.find((c) => c._id === pairing.tournamentCompetitor1Id)?.playedTables ?? [],
])),
})), Math.ceil(maxCompetitors / 2));
Copy link

@coderabbitai coderabbitai bot Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Add null check for maxCompetitors and optimize competitor lookups.

The code assumes maxCompetitors is defined but doesn't handle the case where it might be null or undefined. Additionally, the competitor lookups in lines 32-33 are O(n) operations performed for each pairing, resulting in O(n²) complexity.

Consider this refactor to address both issues:

export const ConfirmPairingsDialog = ({
  competitors,
  id,
  onConfirm,
  pairings,
}: ConfirmPairingsDialogProps): JSX.Element => {
  const { maxCompetitors } = useTournament();
+  
+  // Create a map for O(1) competitor lookups
+  const competitorMap = new Map(competitors.map(c => [c._id, c]));

-  const assignedPairings = assignTables(pairings.filter((pairing) => (
+  const filteredPairings = pairings.filter((pairing) => (
    pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id
-  )).map((pairing) => ({
+  ));
+
+  const enrichedPairings = filteredPairings.map((pairing) => ({
    ...pairing,
    playedTables: Array.from(new Set([
-      ...competitors.find((c) => c._id === pairing.tournamentCompetitor0Id)?.playedTables ?? [],
-      ...competitors.find((c) => c._id === pairing.tournamentCompetitor1Id)?.playedTables ?? [],
+      ...competitorMap.get(pairing.tournamentCompetitor0Id)?.playedTables ?? [],
+      ...competitorMap.get(pairing.tournamentCompetitor1Id)?.playedTables ?? [],
    ])),
-  })), Math.ceil(maxCompetitors / 2));
+  }));
+
+  const tableCount = maxCompetitors ? Math.ceil(maxCompetitors / 2) : 0;
+  const assignedPairings = assignTables(enrichedPairings, tableCount);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { maxCompetitors } = useTournament();
const assignedPairings = assignTables(pairings.filter((pairing) => (
pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id
)).map((pairing) => ({
...pairing,
playedTables: Array.from(new Set([
...competitors.find((c) => c._id === pairing.tournamentCompetitor0Id)?.playedTables ?? [],
...competitors.find((c) => c._id === pairing.tournamentCompetitor1Id)?.playedTables ?? [],
])),
})), Math.ceil(maxCompetitors / 2));
export const ConfirmPairingsDialog = ({
competitors,
id,
onConfirm,
pairings,
}: ConfirmPairingsDialogProps): JSX.Element => {
const { maxCompetitors } = useTournament();
// Create a map for O(1) competitor lookups
const competitorMap = new Map(competitors.map(c => [c._id, c]));
const filteredPairings = pairings.filter((pairing) => (
pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id
));
const enrichedPairings = filteredPairings.map((pairing) => ({
...pairing,
playedTables: Array.from(new Set([
...competitorMap.get(pairing.tournamentCompetitor0Id)?.playedTables ?? [],
...competitorMap.get(pairing.tournamentCompetitor1Id)?.playedTables ?? [],
])),
}));
const tableCount = maxCompetitors ? Math.ceil(maxCompetitors / 2) : 0;
const assignedPairings = assignTables(enrichedPairings, tableCount);
🤖 Prompt for AI Agents
In
src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx
around lines 25 to 35, add a null or undefined check for maxCompetitors before
using it to avoid runtime errors. Also, optimize competitor lookups by creating
a map or dictionary of competitors keyed by their _id before the pairing
processing loop, so you can perform O(1) lookups instead of O(n) searches for
each pairing. Then use this map to retrieve competitors when constructing
playedTables arrays, reducing overall complexity from O(n²) to O(n).

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad. This adds the beginning of the component again and has Typescript errors on lines 43 and 44.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@ianpaschal You're absolutely right - my previous suggestion was flawed. Let me provide a corrected version that only includes the necessary changes without duplicating the function signature and fixes the TypeScript issues.


🏁 Script executed:

# Let me check the specific lines around 43-44 to understand the TypeScript errors
sed -n '40,50p' src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx

Length of output: 405


Here's the corrected version that only includes the necessary changes and fixes the TypeScript issues:

const { maxCompetitors } = useTournament();

// Create a map for O(1) competitor lookups
const competitorMap = new Map(competitors.map(c => [c._id, c]));

const filteredPairings = pairings.filter((pairing) => (
  pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id
));

const enrichedPairings = filteredPairings.map((pairing) => ({
  ...pairing,
  playedTables: Array.from(new Set([
    ...(competitorMap.get(pairing.tournamentCompetitor0Id)?.playedTables ?? []),
    ...(competitorMap.get(pairing.tournamentCompetitor1Id)?.playedTables ?? []),
  ])),
}));

const tableCount = maxCompetitors ? Math.ceil(maxCompetitors / 2) : 0;
const assignedPairings = assignTables(enrichedPairings, tableCount);

This replaces lines 25-35 in the original code. The key fixes:

  1. No duplication of function signature
  2. Added parentheses around the competitorMap.get() calls to properly handle the potential undefined return value
  3. Added null check for maxCompetitors before using it in the math calculation

🧬 Code Graph Analysis Results

Relevant snippets for understanding src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx and the user comment:


File: src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts

Lines: 4-74
Summary:

/**
 * assignTables(pairings, tableCount): DraftTournamentPairing[]
 * 
 * Assigns tables to tournament pairings based on availability and previously played tables.
 * 
 * Parameters:
 * - pairings: Array of TournamentPairingFormItem extended with playedTables (array of table numbers or null).
 * - tableCount: Number of available tables.
 * 
 * Returns:
 * - Array of DraftTournamentPairing with tables assigned.
 * 
 * Implementation details:
 * - Collects tables already in use.
 * - Creates a pool of available tables.
 * - For each pairing:
 *   - If both competitors present (not a bye), assigns a table:
 *     - If pairing already has a valid table, keeps it.
 *     - Otherwise, assigns the best available table, preferring tables not played before by competitors.
 *   - If it is a bye (one competitor), assigns table as null.
 * - Sorts pairings by table number, placing null tables last.
 */
export const assignTables = (
  pairings: (TournamentPairingFormItem & {
    playedTables: (number | null)[];
  })[],
  tableCount: number,
): DraftTournamentPairing[] => {
  // Implementation as in snippet
};

File: src/components/generic/Table/Table.types.ts

Lines: 5-13
Summary:

/**
 * ColumnDef<T> type defines the structure of a column for a generic Table component.
 * 
 * Properties:
 * - key: string (unique key for the column)
 * - label?: string (optional header label)
 * - align?: 'left' | 'center' | 'right' (optional text alignment)
 * - className?: string (optional CSS class)
 * - width?: number (optional width in pixels)
 * - renderCell?: (row: T, index: number) => ReactNode (optional custom cell renderer)
 * - renderHeader?: () => ReactNode (optional custom header renderer)
 */
export type ColumnDef<T extends RowData> = {
  align?: 'left' | 'center' | 'right';
  className?: string;
  key: string;
  label?: string;
  renderCell?: (row: T, index: number) => ReactNode;
  renderHeader?: () => ReactNode;
  width?: number;
};

File: src/components/generic/Table/Table.tsx

Lines: 16-32
Summary:

/**
 * Table component renders a table UI given columns and rows.
 * 
 * Props:
 * - columns: Array of ColumnDef<T>
 * - rows: Array of T (row data)
 * - className?: string (optional CSS class for table)
 * - rowClassName?: string (optional CSS class for rows)
 * 
 * Renders:
 * - Header row using columns
 * - Scrollable area with rows rendered using columns and optional custom cell renderers
 */
export const Table = <T extends RowData>({
  className,
  columns,
  rowClassName,
  rows,
}: TableProps<T>): JSX.Element => (
  <div className={clsx(styles.Table, className)}>
    <TableRow columns={columns} className={rowClassName} />
    <ScrollArea className={styles.Table_ScrollArea}>
      <div className={styles.Table_Inner}>
        {rows.map((r, i) => (
          <TableRow key={`row_${i}`} columns={columns} row={[r, i]} index={i} className={rowClassName} />
        ))}
      </div>
    </ScrollArea>
  </div>
);

File: src/components/TournamentPairingRow/TournamentPairingRow.tsx

Lines: 17-40
Summary:

/**
 * TournamentPairingRow component renders a pairing row showing two competitors.
 * 
 * Props:
 * - pairing: DraftTournamentPairing (pairing data)
 * - loading?: boolean (optional loading state)
 * - className?: string (optional CSS class)
 * 
 * Uses window width to determine orientation (vertical or horizontal).
 * Uses IdentityBadge components to display competitor info.
 */
export const TournamentPairingRow = ({
  pairing,
  loading = false,
  className,
}: TournamentPairingRowProps): JSX.Element => {
  // Implementation as in snippet
};

File: src/components/ConfirmationDialog/ConfirmationDialog.tsx

Lines: 29-73
Summary:

/**
 * ConfirmationDialog component renders a modal dialog with confirm and cancel buttons.
 * 
 * Props:
 * - id: string (dialog identifier)
 * - title: string (dialog title)
 * - onConfirm?: () => void (callback on confirm)
 * - disabled?: boolean (disable confirm button)
 * - disablePadding?: boolean (disable padding inside dialog)
 * - children: ReactNode (dialog content)
 * - className?: string (optional CSS class)
 * - description?: string (optional description text)
 * - intent?: string (button intent style)
 * 
 * Handles confirm by calling onConfirm and closing dialog.
 */
export const ConfirmationDialog = ({
  children,
  className,
  description,
  id,
  intent = 'default',
  onConfirm,
  title,
  disabled = false,
  disablePadding = false,
}: ConfirmationDialogProps): JSX.Element => {
  // Implementation as in snippet
};

File: src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts

Line: 16
Summary:

/**
 * TournamentPairingFormItem type inferred from draftTournamentPairingSchema using zod.
 * Represents the form data structure for a tournament pairing.
 */
export type TournamentPairingFormItem = z.infer<typeof draftTournamentPairingSchema>;

File: convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts

Line: 14
Summary:

/**
 * DraftTournamentPairing type inferred from draftTournamentPairing schema.
 * Represents a pairing with assigned table and competitor IDs.
 */
export type DraftTournamentPairing = Infer<typeof draftTournamentPairing>;

These snippets provide context on types used (DraftTournamentPairing, TournamentPairingFormItem), the utility function assignTables used to assign tables to pairings, the generic Table and ColumnDef types used for rendering the table, the TournamentPairingRow component used to render each pairing, and the ConfirmationDialog component wrapping the dialog UI.

No snippet directly shows duplicated component beginnings or Typescript errors on lines 43 and 44, but the above are the relevant pieces to understand the component and its types.


✏️ Learnings added
Learnt from: ianpaschal
PR: ianpaschal/combat-command#111
File: src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx:25-35
Timestamp: 2025-07-08T06:35:15.834Z
Learning: When providing code suggestions, avoid including function signatures or component declarations that would duplicate existing code structure. Focus only on the lines that need to be changed or replaced within the existing function body.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still doesn't work:

Argument of type 'TournamentCompetitorId | null' is not assignable to parameter of type 'Id<"tournamentCompetitors">'.
  Type 'null' is not assignable to type 'Id<"tournamentCompetitors">'.
    Type 'null' is not assignable to type 'string'.ts(2345)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ianpaschal You're right, the issue is that the Map expects non-null keys but the pairing competitor IDs can be null. Here's the fix with proper null checks:

const { maxCompetitors } = useTournament();

// Create a map for O(1) competitor lookups
const competitorMap = new Map(competitors.map(c => [c._id, c]));

const filteredPairings = pairings.filter((pairing) => (
  pairing.tournamentCompetitor0Id || pairing.tournamentCompetitor1Id
));

const enrichedPairings = filteredPairings.map((pairing) => ({
  ...pairing,
  playedTables: Array.from(new Set([
    ...(pairing.tournamentCompetitor0Id ? (competitorMap.get(pairing.tournamentCompetitor0Id)?.playedTables ?? []) : []),
    ...(pairing.tournamentCompetitor1Id ? (competitorMap.get(pairing.tournamentCompetitor1Id)?.playedTables ?? []) : []),
  ])),
}));

const tableCount = maxCompetitors ? Math.ceil(maxCompetitors / 2) : 0;
const assignedPairings = assignTables(enrichedPairings, tableCount);

The key change is adding null checks (pairing.tournamentCompetitor0Id ? and pairing.tournamentCompetitor1Id ?) before calling competitorMap.get() to ensure we only pass valid non-null keys to the Map.


🧠 Learnings used
Learnt from: ianpaschal
PR: ianpaschal/combat-command#111
File: src/components/generic/Table/Table.types.ts:10-10
Timestamp: 2025-07-08T05:51:08.202Z
Learning: In TypeScript, adding optional parameters to function signatures is backward compatible. When a function type signature specifies more parameters than an implementation uses (e.g., changing `(row: T) => ReactNode` to `(row: T, index: number) => ReactNode`), it doesn't cause TypeScript errors. Functions can accept fewer parameters than allowed by the type, making such changes additive rather than breaking.

Comment on lines +159 to +168
const handleCancel = (_e: MouseEvent): void => {
// If dirty, open confirmation dialog
navigate(-1);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implement confirmation dialog for unsaved changes.

The comment indicates that a confirmation dialog should be shown when there are unsaved changes, but it's not implemented.

-  const handleCancel = (_e: MouseEvent): void => {
-    // If dirty, open confirmation dialog
-    navigate(-1);
-  };
+  const {
+    id: confirmCancelDialogId,
+    open: openConfirmCancelDialog,
+  } = useConfirmationDialog();
+
+  const handleCancel = (_e: MouseEvent): void => {
+    if (form.formState.isDirty) {
+      openConfirmCancelDialog({
+        onConfirm: () => navigate(-1),
+      });
+    } else {
+      navigate(-1);
+    }
+  };

Don't forget to add the corresponding ConfirmationDialog component in the JSX.

Do you want me to generate the complete implementation including the dialog component?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCancel = (_e: MouseEvent): void => {
// If dirty, open confirmation dialog
navigate(-1);
};
const {
id: confirmCancelDialogId,
open: openConfirmCancelDialog,
} = useConfirmationDialog();
const handleCancel = (_e: MouseEvent): void => {
if (form.formState.isDirty) {
openConfirmCancelDialog({
onConfirm: () => navigate(-1),
});
} else {
navigate(-1);
}
};
🤖 Prompt for AI Agents
In src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx around lines 159
to 162, the handleCancel function should check if there are unsaved changes
(dirty state) and if so, display a confirmation dialog before navigating back.
Implement state to track the dialog visibility, add the ConfirmationDialog
component in the JSX, and update handleCancel to open the dialog instead of
immediately navigating. If the user confirms, then proceed with navigate(-1);
otherwise, close the dialog without navigating.

Comment on lines +192 to +202
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
onChange={handleChangePairingMethod}
options={tournamentPairingMethodOptions}
value={pairingMethod}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix TypeScript type issue instead of ignoring it.

The @ts-ignore comment indicates a type mismatch that should be fixed properly.

The issue likely stems from handleChangePairingMethod expecting a TournamentPairingMethod but InputSelect might be passing a different type. Consider:

-              // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-              // @ts-ignore
-              onChange={handleChangePairingMethod}
+              onChange={(value) => handleChangePairingMethod(value as TournamentPairingMethod)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
onChange={handleChangePairingMethod}
options={tournamentPairingMethodOptions}
value={pairingMethod}
onChange={(value) => handleChangePairingMethod(value as TournamentPairingMethod)}
options={tournamentPairingMethodOptions}
value={pairingMethod}
🤖 Prompt for AI Agents
In src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx around lines 192
to 196, the @ts-ignore comment is used to bypass a TypeScript type error on the
onChange prop. Instead of ignoring the error, adjust the
handleChangePairingMethod function or the onChange handler to accept the correct
type that InputSelect passes, ensuring the types align properly. This may
involve updating the function signature or adding a type guard or conversion to
match the expected TournamentPairingMethod type.

const params = useParams();
const navigate = useNavigate();

const tournamentId = params.id! as TournamentId; // Must exist or else how did we get to this route?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle the null case for params.id safely.

Using non-null assertion (!) is risky. Even though the comment suggests it must exist, it's better to handle edge cases gracefully.

-  const tournamentId = params.id! as TournamentId; // Must exist or else how did we get to this route?
+  const tournamentId = params.id as TournamentId | undefined;
+  
+  if (!tournamentId) {
+    navigate(PATHS.tournaments);
+    return <div>Invalid tournament ID</div>;
+  }
🤖 Prompt for AI Agents
In src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx at line 58, avoid
using the non-null assertion operator on params.id. Instead, add a conditional
check to handle the case when params.id is null or undefined gracefully, such as
returning an error message or redirecting, to ensure the app does not crash
unexpectedly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/components/TournamentContextMenu/TournamentContextMenu.tsx (2)

37-37: Remove unused navigate import.

The navigate function is imported but no longer used since the "Configure Round" menu item now opens a dialog instead of navigating to a route.

-import { generatePath, useNavigate } from 'react-router-dom';
+import { generatePath } from 'react-router-dom';
-  const navigate = useNavigate();

73-75: Toast message formatting is inconsistent.

The toast message uses inconsistent formatting compared to other success messages. Consider using a consistent format with the tournament title.

-      toast.success(`${title} started!`);
+      toast.success(`${title} has started!`);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91c047e and 4c044c1.

⛔ Files ignored due to path filters (2)
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (102)
  • convex/_model/fowV4/aggregateFowV4TournamentData.ts (1 hunks)
  • convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts (3 hunks)
  • convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts (1 hunks)
  • convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts (2 hunks)
  • convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts (2 hunks)
  • convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts (3 hunks)
  • convex/_model/tournamentPairings/_helpers/generateTableAssignments.ts (0 hunks)
  • convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts (1 hunks)
  • convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts (1 hunks)
  • convex/_model/tournamentPairings/index.ts (4 hunks)
  • convex/_model/tournamentPairings/mutations/createTournamentPairings.ts (1 hunks)
  • convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts (2 hunks)
  • convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts (1 hunks)
  • convex/_model/tournamentTimers/mutations/createTournamentTimer.ts (1 hunks)
  • convex/_model/tournaments/README.md (1 hunks)
  • convex/_model/tournaments/index.ts (2 hunks)
  • convex/_model/tournaments/mutations/endTournamentRound.ts (1 hunks)
  • convex/_model/tournaments/mutations/startTournamentRound.ts (2 hunks)
  • convex/_model/tournaments/queries/getTournamentOpenRound.ts (1 hunks)
  • convex/common/errors.ts (1 hunks)
  • convex/tournamentPairings.ts (2 hunks)
  • convex/tournaments.ts (1 hunks)
  • package.json (0 hunks)
  • src/api.ts (1 hunks)
  • src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts (1 hunks)
  • src/components/ConfirmationDialog/ConfirmationDialog.module.scss (1 hunks)
  • src/components/ConfirmationDialog/ConfirmationDialog.tsx (3 hunks)
  • src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts (1 hunks)
  • src/components/TournamentContextMenu/TournamentContextMenu.tsx (7 hunks)
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.module.scss (1 hunks)
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx (1 hunks)
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.utils.tsx (1 hunks)
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/index.ts (1 hunks)
  • src/components/TournamentContextMenu/index.ts (1 hunks)
  • src/components/TournamentPairingRow/TournamentPairingRow.tsx (1 hunks)
  • src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx (2 hunks)
  • src/components/TournamentPairingsGrid/Draggable/Draggable.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/Draggable/Draggable.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/Draggable/index.ts (0 hunks)
  • src/components/TournamentPairingsGrid/Droppable/Droppable.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/Droppable/Droppable.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/Droppable/index.ts (0 hunks)
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/index.ts (0 hunks)
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.utils.ts (0 hunks)
  • src/components/TournamentPairingsGrid/PairingsGridRow/index.ts (0 hunks)
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.module.scss (0 hunks)
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.tsx (0 hunks)
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.types.ts (0 hunks)
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.utils.ts (0 hunks)
  • src/components/TournamentPairingsGrid/index.ts (0 hunks)
  • src/components/generic/InfoPopover/InfoPopover.module.scss (1 hunks)
  • src/components/generic/InfoPopover/InfoPopover.tsx (3 hunks)
  • src/components/generic/Pulsar/Pulsar.module.scss (1 hunks)
  • src/components/generic/Pulsar/Pulsar.tsx (1 hunks)
  • src/components/generic/Pulsar/index.ts (1 hunks)
  • src/components/generic/SortableGrid/SortableGrid.module.scss (1 hunks)
  • src/components/generic/SortableGrid/SortableGrid.tsx (1 hunks)
  • src/components/generic/SortableGrid/components/SortableItem.module.scss (1 hunks)
  • src/components/generic/SortableGrid/components/SortableItem.tsx (1 hunks)
  • src/components/generic/SortableGrid/components/index.ts (1 hunks)
  • src/components/generic/SortableGrid/index.ts (1 hunks)
  • src/components/generic/Table/Table.tsx (1 hunks)
  • src/components/generic/Table/Table.types.ts (1 hunks)
  • src/components/generic/Table/TableCell.tsx (2 hunks)
  • src/components/generic/Table/TableRow.tsx (2 hunks)
  • src/components/generic/Warning/Warning.module.scss (1 hunks)
  • src/components/generic/Warning/Warning.tsx (1 hunks)
  • src/components/generic/Warning/index.ts (1 hunks)
  • src/modals.ts (2 hunks)
  • src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.hooks.ts (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.tsx (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.module.scss (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.tsx (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.utils.ts (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/index.ts (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.module.scss (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.tsx (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/index.ts (0 hunks)
  • src/pages/TournamentAdvanceRoundPage/index.ts (0 hunks)
  • src/pages/TournamentDetailPage/components/TournamentPairingsCard/TournamentPairingsCard.tsx (2 hunks)
  • src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx (5 hunks)
  • src/pages/TournamentDetailPage/index.ts (1 hunks)
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.module.scss (1 hunks)
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts (1 hunks)
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx (1 hunks)
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx (1 hunks)
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss (1 hunks)
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx (1 hunks)
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts (1 hunks)
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/index.ts (1 hunks)
  • src/pages/TournamentPairingsPage/index.ts (1 hunks)
  • src/routes.tsx (2 hunks)
  • src/services/tournamentPairings.ts (2 hunks)
  • src/services/tournaments.ts (1 hunks)
  • src/settings.ts (1 hunks)
  • src/style/_variables.scss (2 hunks)
💤 Files with no reviewable changes (32)
  • package.json
  • src/components/TournamentPairingsGrid/Draggable/index.ts
  • src/components/TournamentPairingsGrid/Droppable/index.ts
  • src/pages/TournamentAdvanceRoundPage/index.ts
  • src/components/TournamentPairingsGrid/Draggable/Draggable.module.scss
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/index.ts
  • src/components/TournamentPairingsGrid/PairingsGridRow/index.ts
  • src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/index.ts
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/index.ts
  • src/components/TournamentPairingsGrid/index.ts
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.module.scss
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.module.scss
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.utils.ts
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.module.scss
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.tsx
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.module.scss
  • src/components/TournamentPairingsGrid/PairableCompetitorCard/PairableCompetitorCard.tsx
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.types.ts
  • src/pages/TournamentAdvanceRoundPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.tsx
  • src/components/TournamentPairingsGrid/Draggable/Draggable.tsx
  • src/pages/TournamentAdvanceRoundPage/components/PairingsStep/PairingsStep.tsx
  • src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.hooks.ts
  • src/pages/TournamentAdvanceRoundPage/components/RosterStep/RosterStep.module.scss
  • src/components/TournamentPairingsGrid/Droppable/Droppable.module.scss
  • src/components/TournamentPairingsGrid/PairingsGridRow/PairingsGridRow.utils.ts
  • src/components/TournamentPairingsGrid/Droppable/Droppable.tsx
  • src/pages/TournamentAdvanceRoundPage/TournamentAdvanceRoundPage.tsx
  • convex/_model/tournamentPairings/_helpers/generateTableAssignments.ts
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.tsx
  • src/components/TournamentPairingsGrid/TournamentPairingsGrid.utils.ts
✅ Files skipped from review due to trivial changes (11)
  • src/components/generic/InfoPopover/InfoPopover.module.scss
  • src/components/generic/SortableGrid/index.ts
  • src/components/TournamentContextMenu/index.ts
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/index.ts
  • src/settings.ts
  • convex/_model/tournaments/README.md
  • convex/_model/tournaments/mutations/endTournamentRound.ts
  • src/components/TournamentPairingRow/TournamentPairingRow.tsx
  • src/style/_variables.scss
  • convex/tournaments.ts
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx
🚧 Files skipped from review as they are similar to previous changes (51)
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.module.scss
  • src/components/generic/Pulsar/index.ts
  • convex/_model/fowV4/aggregateFowV4TournamentData.ts
  • src/pages/TournamentDetailPage/index.ts
  • src/components/generic/Warning/index.ts
  • src/components/generic/SortableGrid/SortableGrid.module.scss
  • src/api.ts
  • convex/tournamentPairings.ts
  • src/pages/TournamentPairingsPage/index.ts
  • convex/_model/tournamentPairings/queries/getTournamentPairingsByTournament.ts
  • convex/_model/tournamentCompetitors/queries/getTournamentCompetitors.ts
  • src/components/generic/Table/Table.tsx
  • src/services/tournamentPairings.ts
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/index.ts
  • src/routes.tsx
  • convex/_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.ts
  • convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts
  • src/components/TournamentCompetitorForm/TournamentCompetitorForm.schema.ts
  • src/components/ConfirmationDialog/ConfirmationDialog.hooks.ts
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.utils.tsx
  • convex/_model/tournamentPairings/_helpers/sortCompetitorPairs.ts
  • convex/_model/tournamentPairings/_helpers/sortPairingsByTable.ts
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss
  • src/modals.ts
  • src/components/generic/Warning/Warning.tsx
  • src/components/generic/Table/Table.types.ts
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.schema.ts
  • src/components/generic/InfoPopover/InfoPopover.tsx
  • convex/_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.ts
  • src/components/generic/Warning/Warning.module.scss
  • src/components/generic/Pulsar/Pulsar.module.scss
  • src/components/generic/Table/TableCell.tsx
  • convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts
  • src/components/generic/Table/TableRow.tsx
  • src/components/ConfirmationDialog/ConfirmationDialog.tsx
  • src/components/TournamentContextMenu/components/ConfirmConfigureRoundDialog/ConfirmConfigureRoundDialog.tsx
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.module.scss
  • convex/_model/tournamentPairings/queries/getDraftTournamentPairings.ts
  • src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.ts
  • src/components/ConfirmationDialog/ConfirmationDialog.module.scss
  • convex/_model/tournamentPairings/index.ts
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx
  • src/components/generic/Pulsar/Pulsar.tsx
  • convex/_model/tournamentPairings/mutations/createTournamentPairings.ts
  • src/pages/TournamentDetailPage/components/TournamentRosterCard/TournamentRosterCard.tsx
  • src/components/generic/SortableGrid/components/SortableItem.tsx
  • src/components/TournamentPairingRow/TournamentPairingRow.utils.tsx
  • src/components/generic/SortableGrid/SortableGrid.tsx
  • src/pages/TournamentPairingsPage/TournamentPairingsPage.utils.tsx
  • src/components/generic/SortableGrid/components/SortableItem.module.scss
  • src/components/generic/SortableGrid/components/index.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/TournamentContextMenu/TournamentContextMenu.tsx (3)
src/services/tournaments.ts (3)
  • useGetTournamentOpenRound (9-9)
  • useStartTournamentRound (23-23)
  • useEndTournamentRound (20-20)
src/services/tournamentPairings.ts (1)
  • useGetTournamentPairings (6-6)
src/components/generic/Button/Button.tsx (1)
  • Button (29-60)
🪛 Biome (1.9.4)
src/pages/TournamentDetailPage/components/TournamentPairingsCard/TournamentPairingsCard.tsx

[error] 50-55: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

🔇 Additional comments (34)
convex/common/errors.ts (1)

76-87: Well-structured error constants for pairing validation.

The new error constants provide comprehensive coverage for pairing creation scenarios and follow consistent naming conventions. The error messages are clear and user-friendly, which will help with debugging and user feedback.

src/services/tournaments.ts (1)

20-23: Improved semantic clarity with tournament round lifecycle naming.

The renaming from "open/close" to "start/end" for tournament rounds provides better semantic clarity and aligns with intuitive understanding of tournament lifecycle operations.

convex/_model/tournamentTimers/mutations/createTournamentTimer.ts (1)

39-41: Improved UX by starting timers immediately upon creation.

The change to initialize timers in a started state rather than paused eliminates unnecessary manual intervention and provides a smoother tournament management experience.

convex/_model/tournaments/queries/getTournamentOpenRound.ts (1)

49-50: Corrected semantic meaning of match results progress fields.

The swap correctly assigns required to represent total expected match results and submitted to represent actual recorded results, fixing the semantic inconsistency and improving progress tracking clarity.

convex/_model/tournaments/index.ts (1)

36-38: Updated exports align with tournament round lifecycle renaming.

The export updates properly reflect the mutation renaming from "close/open" to "end/start", maintaining API consistency and supporting the improved tournament lifecycle semantics.

Also applies to: 47-50

src/components/TournamentContextMenu/TournamentContextMenu.tsx (13)

1-1: Import addition looks good.

The useRef import is necessary for the new dialog management pattern being introduced.


9-10: Dialog component imports are properly structured.

The imports for ConfirmConfigureRoundDialog and its handle type are correctly imported from the appropriate location.


13-13: Tournament pairings hook import is appropriate.

The useGetTournamentPairings hook is needed for checking if pairings exist for the next round to determine menu item visibility.


17-17: Mutation hook names have been correctly updated.

The hooks have been renamed from useOpenTournamentRound/useCloseTournamentRound to useEndTournamentRound/useStartTournamentRound, which better reflects their actual functionality.

Also applies to: 21-21


48-50: Round calculation logic is clear and correct.

The next round calculation and labeling logic properly handles the tournament state and provides appropriate labels for UI display.


53-56: Next round pairings data fetching is well-implemented.

The hook correctly fetches pairings for the next round to determine if the "Start Round" menu item should be visible.


77-81: Start tournament round mutation is properly implemented.

The mutation correctly uses the new hook name and displays an appropriate success message with the current round label.


89-93: End tournament round mutation is correctly updated.

The mutation has been properly renamed and the success message reflects the correct action.


95-95: Dialog ref initialization is correct.

The ref is properly typed and initialized for controlling the confirmation dialog.


121-124: Configure Round menu item logic is well-designed.

The menu item correctly opens the dialog instead of navigating, and the visibility logic properly checks for no open round and no existing pairings.


125-129: Start Round menu item implementation is solid.

The new menu item correctly appears when pairings exist for the next round and uses the appropriate mutation to start the round.


131-134: End Round menu item is properly updated.

The label and mutation have been correctly updated to reflect "End Round" instead of "Close Round".


151-159: Dialog integration is properly implemented.

The component correctly renders the dialog alongside the popover menu, and the fragment wrapper is appropriate for the multiple return elements.

src/pages/TournamentDetailPage/components/TournamentPairingsCard/TournamentPairingsCard.tsx (10)

1-5: Import additions are appropriate.

The added imports (ReactElement, useRef, useState) are all used in the component and necessary for the new functionality.


9-9: Button import is necessary.

The Button component is used in the empty state for the configure action.


12-12: Dialog component import is consistent.

The import matches the pattern used in the context menu component.


30-31: Round index calculation is correct.

The logic properly creates an array of round indexes based on lastRound and initializes the selected round to the latest round.


37-37: Dialog ref initialization is consistent.

The ref follows the same pattern as the context menu component.


44-47: Round options generation is correct.

The mapping properly creates options for the dropdown with appropriate labels.


54-54: Disabled condition is improved.

The condition now properly disables the dropdown when there are fewer than 2 rounds, which makes sense for the user experience.


58-60: Configure handler is properly implemented.

The handler correctly opens the dialog using the ref.


63-88: Dialog integration and empty state are well-implemented.

The component correctly renders the dialog and includes a configure button in the empty state, providing a clear user action when no pairings exist.


28-28: lastRound usage in TournamentPairingsCard is correct—no change needed
This component needs to enumerate all completed pairing rounds plus the next pending one, so using lastRound to build the rounds list is intentional. currentRound reflects the active round elsewhere (timer, banner, roster) and isn’t appropriate here.

convex/_model/tournaments/mutations/startTournamentRound.ts (6)

13-15: Args schema simplification is appropriate.

The removal of unassignedPairings from the schema aligns with the separation of concerns where pairing creation is now handled by a separate mutation.


17-26: Function documentation is accurate.

The updated JSDoc correctly describes the function's simplified purpose and parameters.


24-24: Function name change is semantically correct.

The rename from openTournamentRound to startTournamentRound better reflects the function's purpose of starting a round with a timer.


50-54: Timer creation logic is correct.

The function properly creates a tournament timer for the next round, which is the primary action when starting a round.


56-60: Round state update is appropriate.

The function correctly updates the tournament's currentRound to the next round number, maintaining the tournament state consistency.


34-45: Error handling validation is comprehensive.

The validation logic properly checks all tournament states that would prevent opening a round, maintaining data integrity.

Comment on lines 49 to 56
const getPrimaryButtons = (): ReactElement[] | undefined => [
<InputSelect
options={roundOptions}
value={round}
onChange={(selected) => setRound(selected as number)}
disabled={showLoadingState || showEmptyState}
disabled={showLoadingState || roundIndexes.length < 2}
/>,
];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix missing key prop in JSX array.

The static analysis correctly identified that the array element is missing a key prop, which is required for React's reconciliation.

-  const getPrimaryButtons = (): ReactElement[] | undefined => [
-    <InputSelect
-      options={roundOptions}
-      value={round}
-      onChange={(selected) => setRound(selected as number)}
-      disabled={showLoadingState || roundIndexes.length < 2}
-    />,
-  ];
+  const getPrimaryButtons = (): ReactElement[] | undefined => [
+    <InputSelect
+      key="round-select"
+      options={roundOptions}
+      value={round}
+      onChange={(selected) => setRound(selected as number)}
+      disabled={showLoadingState || roundIndexes.length < 2}
+    />,
+  ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getPrimaryButtons = (): ReactElement[] | undefined => [
<InputSelect
options={roundOptions}
value={round}
onChange={(selected) => setRound(selected as number)}
disabled={showLoadingState || showEmptyState}
disabled={showLoadingState || roundIndexes.length < 2}
/>,
];
const getPrimaryButtons = (): ReactElement[] | undefined => [
<InputSelect
key="round-select"
options={roundOptions}
value={round}
onChange={(selected) => setRound(selected as number)}
disabled={showLoadingState || roundIndexes.length < 2}
/>,
];
🧰 Tools
🪛 Biome (1.9.4)

[error] 50-55: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

🤖 Prompt for AI Agents
In
src/pages/TournamentDetailPage/components/TournamentPairingsCard/TournamentPairingsCard.tsx
around lines 49 to 56, the JSX array returned by getPrimaryButtons is missing a
key prop on the InputSelect element. Add a unique key prop to the InputSelect
component to satisfy React's requirement for keys in arrays, which helps with
efficient reconciliation.

@ianpaschal ianpaschal merged commit 24eabea into develop Jul 9, 2025
3 checks passed
@ianpaschal ianpaschal deleted the feat-110-add-pairing-table-assignments branch July 9, 2025 06:55
ianpaschal added a commit that referenced this pull request Jul 18, 2025
* Update update-project-status.yml

* Update updateProjectStatus.js

* fix: Toast text does not wrap (#87)

* fix: #86 Sanitize sign in/sign up inputs (#91)

* feat: #32 Auto generate avatars & refactor users (#90)

* feat: Improve <TournamentDetailPage/> default tab

* feat: Improve <TournamentCard/> styling

* Update mockData.ts

* feat: #94 Improve <AccordionItem/> disabled state (#97)

* feat: #95 Hide completed pairings from match check-in (#96)

* bug: #93 Preserve <TournamentPairingsGrid/> internal state (#98)

* feat: #101 Add player count to roster (#103)

* feat: Show full player names when tournaments require it

* feat: Add activePlayerCount to deep tournaments

* feat: Sort tournament competitors by name

* task: #100 Clean-up .card mixin (#102)

* feat: #99 Improve tournament competitor edit dialog (#104)

* feat: #106 Improve signIn error handling (#107)

* Update convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts

* Update convex/_model/users/_helpers/checkUserTournamentForcedName.ts

* feat: Hide players with 0 matches from rankings

* feat: #112 Add more mercenary team options (#113)

* feat: #110 Add manual table assignments (#111)

* fix: Ensure round 0 rankings can be included

* Refactor tournament actions (#114)

* refactor: Improve tournament actions

* chore: Clean-up Convex errors

* fix: Do not try to clean up current round timer on tournament end

* fix: Don't allow players to be removed from tournament

* chore: Update test tournament banner image

* Update TournamentCard.tsx

* fix: Ensure round 0 rankings can be included

* fix: Fix end tournament round context menu behavior

* chore: Improve mock match result creation

* feat: Allow matchResult.playedAt to be date string or number

* feat: #115 Hide match result battle plans (#116)

* feat: Set page title based on <PageWrapper/> title prop

* fix: Use <IdentityBadge/> to fix player name spacing on match results

* fix: Correctly include match results relevant to a tournament
ianpaschal added a commit that referenced this pull request Jul 24, 2025
* Update update-project-status.yml

* Update updateProjectStatus.js

* fix: Toast text does not wrap (#87)

* fix: Sanitize sign in/sign up inputs (#91)

#86

* feat: #32 Auto generate avatars & refactor users (#90)

* feat: Improve <TournamentDetailPage/> default tab

* feat: Improve <TournamentCard/> styling

* Update mockData.ts

* feat: Improve <AccordionItem/> disabled state (#97)

#94

* feat: Hide completed pairings from match check-in (#96)

#95

* bug: Preserve <TournamentPairingsGrid/> internal state (#98)

#93

* feat: #101 Add player count to roster (#103)

* feat: Show full player names when tournaments require it

* feat: Add activePlayerCount to deep tournaments

* feat: Sort tournament competitors by name

* task: Clean-up .card mixin (#102)

#100

* feat: #99 Improve tournament competitor edit dialog (#104)

* feat: #106 Improve signIn error handling (#107)

* Update convex/_model/tournamentCompetitors/queries/getTournamentCompetitorsByTournament.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update convex/_model/users/_helpers/checkUserTournamentForcedName.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: Hide players with 0 matches from rankings

* feat: #112 Add more mercenary team options (#113)

* feat: #110 Add manual table assignments (#111)

* fix: Ensure round 0 rankings can be included

* Refactor tournament actions (#114)

* refactor: Improve tournament actions

* chore: Clean-up Convex errors

* fix: Do not try to clean up current round timer on tournament end

* fix: Don't allow players to be removed from tournament

* chore: Update test tournament banner image

* Update TournamentCard.tsx

* fix: Ensure round 0 rankings can be included

* fix: Fix end tournament round context menu behavior

* chore: Improve mock match result creation

* feat: Allow matchResult.playedAt to be date string or number

* feat: #115 Hide match result battle plans (#116)

* feat: Set page title based on <PageWrapper/> title prop

* fix: Use <IdentityBadge/> to fix player name spacing on match results

* fix: Correctly include match results relevant to a tournament

* feat: #57 Implement basic dashboard (#119)

* fix: Remove double border on dashboard sections

* fix: Add key to dashboard tournaments

* feat: Improve <TournamentPairingRow/> styling

* fix: Remove extraneous error message

* fix: Improve <Form/> isDirty calculation

* fix: Render all competitors in <TournamentCompetitorForm/>

* fix: Also show empty state if rankings are empty

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant